package com.weixing.mall.provider.controller.front;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.weixing.mall.base.constant.DatePattern;
import com.weixing.mall.base.enums.IdGenTagEnum;
import com.weixing.mall.base.enums.WhetherEnum;
import com.weixing.mall.base.exception.BusinessException;
import com.weixing.mall.base.wrapper.Result;
import com.weixing.mall.base.wrapper.ResultUtil;
import com.weixing.mall.core.annotation.NoNeedAccess;
import com.weixing.mall.core.annotation.NoRepeatSubmit;
import com.weixing.mall.core.support.BaseController;
import com.weixing.mall.core.support.auth.AuthUtil;
import com.weixing.mall.core.utils.EnvUtil;
import com.weixing.mall.core.utils.RequestUtil;
import com.weixing.mall.leaf.api.service.IdGenServiceRpc;
import com.weixing.mall.pay.*;
import com.weixing.mall.pay.channel.wx.WxTradeTypeEnum;
import com.weixing.mall.pay.enums.PayChannelEnum;
import com.weixing.mall.pay.properties.PayProperties;
import com.weixing.mall.provider.api.model.vo.PaySetVo;
import com.weixing.mall.provider.api.service.PaySetFeign;
import com.weixing.mall.provider.model.domain.Order;
import com.weixing.mall.provider.model.domain.OrderPay;
import com.weixing.mall.provider.model.dto.OrderPayDto;
import com.weixing.mall.provider.model.enums.PayStatusEnum;
import com.weixing.mall.provider.service.IOrderPayService;
import com.weixing.mall.provider.service.IOrderService;
import com.weixing.mall.provider.util.OrderUtil;
import com.weixing.mall.provider.util.WechatUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 订单支付相关接口
 *
 * @author  Small
 * @date  2019/11/29 10:52
 * @since  1.0
 **/
@RestController
@RequestMapping("pay")
@Slf4j
public class OrderPayController extends BaseController {

    @Autowired
    private IOrderPayService orderPayService;
    @Autowired
    private IdGenServiceRpc idGenServiceRpc;
    @Autowired
    private PayProperties payProperties;
    @Autowired
    private IOrderService orderService;
    @Autowired
    private PaySetFeign paySetFeign;

    /**
     * 发起付款
     *
     * @param request HttpServletRequest
     * @param dto     支付参数
     * @return        结果
     */
    @PostMapping("")
    @NoRepeatSubmit
    public Result pay(HttpServletRequest request, @RequestBody OrderPayDto dto) {
        Date now = new Date();
        PayChannelEnum payChannel = dto.getPayChannel();
        // 1、判断订单是否存在
        Order order = orderService.getById(dto.getOrderId());
        if (null == order) {
            return ResultUtil.error("订单不存在");
        }
        // 2、判断订单是否已支付
        OrderPay orderPay = null;
        // 2.1、查看订单表中支付状态，如果状态为已支付，则给前台返回已支付提示
        if (WhetherEnum.YES.getValue().equals(order.getIsPay())) {
            return ResultUtil.error("订单已支付，请勿重复发起");
        } else {
            // 2.2、如果订单表中支付状态为未支付，则查询订单支付表，查看详细支付状态
            orderPay = orderPayService.findByOrderId(order.getId());
            if (null != orderPay) {

            } else {
                // 3、创建订单支付记录
                String outTradeNo = genOutTradeNo(now, payChannel);
                orderPay = createOrderPay(payChannel, order, outTradeNo, dto.getTradeType());
                orderPayService.save(orderPay);
            }
        }
        // 非正式环境放过支付
        if (!EnvUtil.isPro()) {
            order.setIsPay(WhetherEnum.YES.getValue());
            orderPay.setTransNo("test" + RandomUtil.randomNumbers(8));
            orderPay.setPayStatus(PayStatusEnum.PAY_SUCCESS.getValue());
            orderPay.setPayTime(new Date());
            orderPayService.execNotifySuccess(order, orderPay);
            return ResultUtil.success(PayResult.success(null));
        }


        // 支付配置信息

        PaySetVo paySetVo = getPaySetVo(now, payChannel.getValue());
        // payment
        IPayment payment = getPayment(dto.getTradeType(), payChannel, paySetVo);

        // 发起支付
        PayResult payResult = raisePay(request, payment, order, orderPay, dto, paySetVo);
        if (payResult.isSuccess()) {
            order.setOutTradeNo(orderPay.getOutTradeNo());
            orderService.updateById(order);
            orderPay.setPayStatus(PayStatusEnum.PAY_ING.getValue());
        }
        return ResultUtil.success(payResult);
    }

    private String genOutTradeNo(Date now, PayChannelEnum payChannel) {
        long transNo = idGenServiceRpc.segmentKey(IdGenTagEnum.ORDER_TRADE_NO.getValue());
        return OrderUtil.genTradeNo(payChannel, transNo, now);
    }

    /**
     * 唤起支付
     *
     * @param order
     * @param orderPay
     * @return PayResult
     */
    private PayResult raisePay(HttpServletRequest request, IPayment payment, Order order, OrderPay orderPay, OrderPayDto dto, PaySetVo paySetVo) {
        PayParam payParam = new PayParam();
        payParam.setOutTransNo(orderPay.getOutTradeNo());
        payParam.setAmount(EnvUtil.isPro() ? order.getActualPrice() : new BigDecimal("0.01"));
        payParam.setBody("订单支付");
        String openId = AuthUtil.getMemberExt().getOpenId();
        if (StrUtil.isNotEmpty(dto.getCode())) {
//            openId = WechatUtil.getOpenIdByCode(dto.getCode());
            openId = WechatUtil.getOpenIdByCode(dto.getCode(), paySetVo);
            if (StrUtil.isEmpty(openId)) {
                throw new BusinessException("支付失败");
            }
        }

        if (WxTradeTypeEnum.XCX.name().equals(dto.getTradeType())) {
            openId = AuthUtil.getMemberExt().getXcxopenid();
        }

        payParam.setOpenid(openId);
        payParam.setRemoteIp(RequestUtil.getRemoteAddr(request));
        return payment.pay(payParam);
    }

    /**
     * 生成payment
     *
     * @param tradeType  交易类型
     * @param payChannel 支付渠道
     * @param paySetVo    支付参数
     * @return IPayment
     */
    private IPayment getPayment(String tradeType, PayChannelEnum payChannel, PaySetVo paySetVo) {
        IPayFactory payFactory = PayFactoryBuilder.buildPayFactory(payChannel);
        IPayment payment = null;
        if (null != paySetVo) {
            payment = payFactory.getPayment(tradeType, paySetVo.getParamSet());
        }
        return payment;
    }

    private PaySetVo getPaySetVo(Date now, String channelCode) {
        Result<List<PaySetVo>> paySetResult = paySetFeign.loadPaySet(channelCode);
        PaySetVo paySetVo = null;
        if (paySetResult.isSuccess()) {
            List<PaySetVo> setVoList = paySetResult.getData();
            if (CollUtil.isNotEmpty(setVoList)) {
                // 如果可用配置只有一个就使用该配置，如果可用配置为多个，则选用订单创建日期在配置生效时间段内的配置
                int size = setVoList.size();
                if (size == 1) {
                    paySetVo = setVoList.get(0);
                } else {
                    for (int i = 0; i < size; i++) {
                        PaySetVo vo = setVoList.get(i);
                        Date effectStartTime = vo.getEffectStartTime();
                        Date effectEndTime = vo.getEffectEndTime();
                        if (now.compareTo(effectStartTime) >= 0 && now.compareTo(effectEndTime) <= 0) {
                            paySetVo = vo;
                            break;
                        }
                    }
                }

            }
        }
        return paySetVo;
    }


    /**
     * 支付查询
     *
     * @param orderId  订单id
     * @return  结果
     */
    @GetMapping("query/{orderId}")
    @NoNeedAccess
    public Result<QueryResult> query(@PathVariable Long orderId) {
        Order order = orderService.getById(orderId);
        // 1、判断订单是否存在
        if (null == order) {
            return ResultUtil.error("订单不存在");
        }
        // 2、判断订单支付状态
        if (WhetherEnum.YES.getValue().equals(order.getIsPay())) {
            QueryResult queryResult = QueryResult.tradeSuccess();
            queryResult.setOutTradeNo(order.getOutTradeNo());
            queryResult.setIdentificationCode(order.getIdentificationCode());
            return ResultUtil.success(queryResult);
        }
        // 3、如果订单支付状态为未支付则调用支付接口查询实际支付状态
        OrderPay orderPay = orderPayService.findByOrderId(orderId);
        String outTradeNo = orderPay.getOutTradeNo();
        String tradeType = orderPay.getTradeType();
        // 4、根据支付流水号解析出支付渠道
        int payChannel = Integer.parseInt(String.valueOf(outTradeNo.charAt(14)));
        // 5、根据支付渠道和交易类型创建对应的支付接口
        Date now = DateUtil.parse(outTradeNo.substring(0, 14), DatePattern.PURE_DATETIME_PATTERN);
        // 支付配置信息
        PayChannelEnum iEnum = PayChannelEnum.getByCode(payChannel);
        PaySetVo paySetVo = getPaySetVo(now, iEnum.getValue());
        IPayment payment = getPayment(tradeType, iEnum, paySetVo);
        // 6、发起查询请求
        QueryResult queryResult = payment.query(outTradeNo);
        if (queryResult.isTradeSuccess()) {
            order.setIsPay(WhetherEnum.YES.getValue());
            orderPay.setPayStatus(PayStatusEnum.PAY_SUCCESS.getValue());
            orderPay.setPayTime(new Date());
            orderPay.setTransNo(queryResult.getTradeNo());
            orderPayService.execQuerySuccess(order, orderPay);
        }
        queryResult.setOutTradeNo(outTradeNo);
        queryResult.setIdentificationCode(order.getIdentificationCode());
        return ResultUtil.success(queryResult);
    }


    /**
     * 支付回调
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @param payChannel 支付渠道
     */
    @RequestMapping("notify/{payChannel}")
    @NoNeedAccess
    public void payNotify(HttpServletRequest request,
                          HttpServletResponse response,
                          @PathVariable PayChannelEnum payChannel) {
        IPayFactory payFactory = PayFactoryBuilder.buildPayFactory(payChannel);
        INotify notify = null;
        if (PayProperties.ParamFromEnum.PROP.equals(payProperties.getParamFrom())) {
            notify = payFactory.getNotify(payProperties);
        } else {
            Result<List<PaySetVo>> paySetResult = paySetFeign.loadPaySet(payChannel.getValue());
            if (paySetResult.isSuccess()) {
                List<PaySetVo> setVoList = paySetResult.getData();
                List<Map<String, Object>> setVoMapList = Lists.newArrayList();
                if (CollUtil.isNotEmpty(setVoList)) {
                    // 直接返回配置列表
                    setVoList.forEach(paySetVo -> {
                        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(paySetVo);
                        setVoMapList.add(stringObjectMap);
                    });
                }
                notify = payFactory.getNotify(setVoMapList);
            }
        }
        NotifyResult notifyResult = notify.doNotify(request);
        String notifyReturnStr = notify.success();
        if (!notifyResult.isSuccess()) {
            // 回调不成功
            notifyReturnStr = notify.fail();
        }
        if (notifyResult.isSignVerifyFail()) {
            // 验签失败
            notifyReturnStr = notify.fail();
        }
        // 校验金额是否一致
        NotifyResp notifyResp = notifyResult.getNotifyResp();
        // 平台订单号
        String outTradeNo = notifyResp.getOutTradeNo();
        // 通道订单号
        String tradeNo = notifyResp.getTradeNo();
        OrderPay orderPay = orderPayService.findByOutTradeNo(outTradeNo);
        Long orderId = orderPay.getOrderId();
        Order order = orderService.getById(orderId);
        // 校验金额
        if (!EnvUtil.isDev() && order.getActualPrice().doubleValue() != notifyResp.getTotalAmount().doubleValue()) {
            // 金额校验失败
            notifyReturnStr = notify.fail();
        }
        // 判断交易是否成功
        if (notifyResult.isTradeSuccess()) {
            order.setIsPay(WhetherEnum.YES.getValue());
            orderPay.setTransNo(tradeNo);
            orderPay.setPayStatus(PayStatusEnum.PAY_SUCCESS.getValue());
            orderPay.setPayTime(new Date());
            orderPayService.execNotifySuccess(order, orderPay);
        }
        PrintWriter pw = null;
        try {
            response.reset();
            pw = response.getWriter();
            pw.write(notifyReturnStr);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != pw) {
                pw.close();
            }
        }
    }


    @RequestMapping("notify/test")
    @NoNeedAccess
    public void payNotifyTest(HttpServletRequest request,
                              HttpServletResponse response,
                              @PathVariable PayChannelEnum payChannel) {
        String notifyReturnStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        PrintWriter pw = null;
        try {
            response.reset();
            pw = response.getWriter();
            pw.write(notifyReturnStr);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != pw) {
                pw.close();
            }
        }
    }


    /**
     * 创建orderpay对象
     *
     * @param payChannel  支付渠道
     * @param order         订单
     * @param outTradeNo   平台订单号
     * @date  2019/12/9 20:03
     * @return OrderPay
     */
    private OrderPay createOrderPay(PayChannelEnum payChannel, Order order, String outTradeNo, String tradeType) {
        OrderPay payOrder = new OrderPay();
        payOrder.setOrderId(order.getId());
        payOrder.setOutTradeNo(outTradeNo);
        payOrder.setPayStatus(PayStatusEnum.NOT_PAY.getValue());
        payOrder.setPayChannel(payChannel.getCode());
        payOrder.setTradeType(tradeType);
        return payOrder;
    }

}
